#pragma once
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <string>
#include <vector>
#include <stdexcept>

#ifdef _WIN32
#include <openssl/applink.c>
#endif

class crypto_handler {
public:
    static void handleErrors() {
        ERR_print_errors_fp(stderr);
        abort();
    }

    static std::vector<unsigned char> encrypt(const std::string& data, const std::string& certFile) {
        FILE* fp = fopen(certFile.c_str(), "r");
        if (!fp) {
            throw std::runtime_error("Error opening certificate file");
        }

        X509* cert = PEM_read_X509(fp, NULL, NULL, NULL);
        fclose(fp);
        if (!cert) {
            throw std::runtime_error("Error reading certificate");
        }

        EVP_PKEY* pubkey = X509_get_pubkey(cert);
        X509_free(cert);
        if (!pubkey) {
            throw std::runtime_error("Error getting public key from certificate");
        }

        EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pubkey, NULL);
        if (!ctx) handleErrors();

        if (EVP_PKEY_encrypt_init(ctx) <= 0) handleErrors();

        size_t outlen;
        if (EVP_PKEY_encrypt(ctx, NULL, &outlen, (unsigned char*)data.c_str(), data.length()) <= 0) handleErrors();

        std::vector<unsigned char> out(outlen);
        if (EVP_PKEY_encrypt(ctx, out.data(), &outlen, (unsigned char*)data.c_str(), data.length()) <= 0) handleErrors();

        EVP_PKEY_CTX_free(ctx);
        EVP_PKEY_free(pubkey);

        return out;
    }

    static std::string decrypt(const std::vector<unsigned char>& encryptedData, const std::string& keyFile) {
        FILE* fp = fopen(keyFile.c_str(), "r");
        if (!fp) {
            throw std::runtime_error("Error opening private key file");
        }

        EVP_PKEY* privkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
        fclose(fp);
        if (!privkey) {
            throw std::runtime_error("Error reading private key");
        }

        EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(privkey, NULL);
        if (!ctx) handleErrors();

        if (EVP_PKEY_decrypt_init(ctx) <= 0) handleErrors();

        size_t outlen;
        if (EVP_PKEY_decrypt(ctx, NULL, &outlen, encryptedData.data(), encryptedData.size()) <= 0) handleErrors();

        std::vector<unsigned char> out(outlen);
        if (EVP_PKEY_decrypt(ctx, out.data(), &outlen, encryptedData.data(), encryptedData.size()) <= 0) handleErrors();

        EVP_PKEY_CTX_free(ctx);
        EVP_PKEY_free(privkey);

        return std::string(out.begin(), out.end());
    }
};
